#!/usr/bin/env python
# Create: 2018/10/12
__author__ = '749B'
"""Web 微信
获取给手机扫的登录二维码
获取登录用户的头像
获取用户信息
获取联系人列表
接收消息
发送消息
"""

from bs4 import BeautifulSoup
import requests
import time
import re

s = requests.Session()
params = {
    'appid': 'wx782c26e4c19acffb',
    'redirect_uri': 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage',
    'fun': 'new',
    'lang': 'zh_CN',
    '_': int(time.time() * 1000)
}
r1 = s.get('https://login.wx.qq.com/jslogin', params=params)
print(r1.text)
uuid = re.findall('window.QRLogin.uuid = "(.*)"', r1.text)
uuid = uuid[0]
print(uuid)
r2 = s.get('https://login.weixin.qq.com/qrcode/' + uuid)
with open('%s.jpeg' % uuid, 'wb') as f:
    f.write(r2.content)


r = 1541893233750 - time.time() * 1000
params = {
    'loginicon': 'true',
    'uuid': uuid,
    'tip': '0',
    'r': r,
    '_': time.time() * 1000
}
code = '408'
r3 = None
while code == '408':
    r3 = s.get('https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login', params=params)
    print(r3.text)
    code = re.findall("window.code=(\d\d\d)", r3.text)
    code = code[0]
    if code == '201':
        userAvatar = re.findall("window.userAvatar = '(.*)';", r3.text)
        userAvatar = userAvatar[0]
        """生成头像的图片
        import base64
        strs = userAvatar.replace("data:img/jpg;base64,", "")
        imgdata = base64.b64decode(strs)
        with open('头像.jpg', 'wb') as f:
            f.write(imgdata)
        """
        # 201收到响应之后，继续发送长轮询
        params['_'] += 1
        params['r'] = 1541893233750 - time.time() * 1000
        r3 = s.get('https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login', params=params)
        code = re.findall("window.code=(\d\d\d)", r3.text)
        code = code[0]
    # 每次请求只是自增1，这样就和准确的时间有误差了
    # 应该是用这个来控制长时间不扫码，服务器就会拒绝请求
    params['_'] += 1
    # 是什么不知道，但是每次都是按时间戳的1000倍减少的
    params['r'] = 1541893233750 - time.time() * 1000

print(r3.text)
redirect_uri = re.findall("window.redirect_uri=\"(.*)\";", r3.text)[0]
print(redirect_uri)

params = {
    'fun': 'new',
    'version': 'v2'
}
r4 = s.get(redirect_uri, params=params)
print(r4.text)
soup = BeautifulSoup(r4.text, features='html.parser')
target = soup.find('error')
ticket = {}
for item in target.children:
    ticket[item.name] = item.text
print(ticket)

# 进入主页面
url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit"
params = {
    # 'r': '1976951002',  # 这是什么不知道
    'lang': 'zh_CN',
    'pass_ticket': ticket['pass_ticket'],
}
json_data = {"BaseRequest": {
    "Uin": ticket['wxuin'],
    "Sid": ticket['wxsid'],
    "Skey": ticket['skey'],
    "DeviceID": "e189955857229638",
}}
r5 = s.post(url, params=params, json=json_data)
r5.encoding = r5.apparent_encoding
# print(r5.apparent_encoding)
# print(r5.text)

# 把页面的内容生成一个html来展示
import json
obj = json.loads(r5.text)
user = obj['User']
f = open('wx.html', 'w', encoding='utf-8')
f.write('<meta charset="UTF-8">\n')
f.write("<h1>Web 微信</h1>\n")
f.write("<h3>用户名：%s</h3>\n" % user['NickName'])
contactList = obj['ContactList']
f.write("<h3>最近联系人</h3>\n")
f.write("<ul>\n")
for i in contactList:
    # print(i)
    user_info = i['RemarkName'] or i['NickName']
    if i['Sex']:
        sex = "男" if i['Sex'] == 1 else "女"
        user_info = "%s(%s)" % (user_info, sex)
    if i['Signature']:
        user_info = "%s: %s" % (user_info, i['Signature'])
    f.write("<li>%s</li>\n" % user_info)
f.write("</ul>\n")
mpSubscribeMsgList = obj['MPSubscribeMsgList']
f.write("<h3>最近公众号信息</h3>\n")
f.write("<ul>\n")
for i in mpSubscribeMsgList:
    # print(i)
    f.write("<li>%s</li>\n" % i['NickName'])
    f.write("<ul>\n")
    for article in i['MPArticleList']:
        f.write("<li><a href='%s'>%s</a></br>%s</li>\n" % (article['Url'], article['Title'], article['Digest']))
    f.write("</ul>\n")
f.write("</ul>\n")
f.close()


# 获取所有联系人信息，这个请求是会验证cookie的
url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact"
params = {
    'pass_ticket': ticket['pass_ticket'],
    'r': int(time.time() * 1000),
    'seq': '0',
    'skey': ticket['skey']
}
r6 = s.get(url, params=params)
# r6.encoding = r6.apparent_encoding  # apparent_encoding 自动获取到的编码是错的
# print(r6.apparent_encoding)
r6.encoding = "utf-8"  # 直接指定"utf-8"就对了
# 自动获取到的编码是"Windows-1254"这个是别名，正式名称是"cp1254"。
# 写哪个都一样的，不过问题是，不能用，编码是错的，大概就是误导我们的
# Python36/Lib/encodings/aliases.py 这个文件里有所有编码的别名的对应关系
# print(r6.text)
# with open('contact.txt', 'w', encoding='utf-8') as f:
#     f.write(r6.text)

# 找到联系人信息
name = "联系人名字"
msg = "测试消息，收到请回复。"  # 发中文会有乱码，不过这个是json序列化的问题
to_user_obj = None
obj = json.loads(r6.text)
for member in obj['MemberList']:
    if name in member["NickName"] or name == member["RemarkName"]:
        to_user_obj = member
        break
if to_user_obj:
    print(to_user_obj["Signature"])
else:
    to_user_obj = user
    print("未找到联系人")
# 发消息
url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg"
params = {
    'lang': 'zh_CN',
    'pass_ticket': ticket['pass_ticket'],
}
# 这个字典之前用过，之前里面只有BaseRequest
# 现在保留BaseRequest，还要加上Msg
time_stamp = time.time() * 1000
json_data['Msg'] = {
    'ClientMsgId': time_stamp,
    'Content': msg,
    'FromUserName': user["UserName"],  # 之前获取用户信息里拿到的
    'LocalID': time_stamp,
    'ToUserName': to_user_obj["UserName"],
    'Type': 1,  # 这个是消息类型，1是文本
}
json_data['Scene'] = 0  # 不知道是啥，照着写
# r7 = s.post(url=url, params=params, json=json_data)  # 这个不能发中文
headers = s.headers
headers['Content-Type'] = 'application/json'
data = json.dumps(json_data, ensure_ascii=False).encode('utf-8')
r7 = s.post(url=url, params=params, headers=headers, data=data)
print(r7.text)


# 收消息
url = "https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck"
sync_key = json.loads(r5.text)["SyncKey"]
params = {
    'skey': ticket['skey'],
    'sid': ticket['wxsid'],
    'uin': ticket['wxuin'],
    'deviceid': 'e941046347280021',  # 这个一直在变，貌似没啥影响
    '_': int(time.time() * 1000) - 26846,
}
print("持续接收消息")
while True:
    sync_key_list = []
    for item in sync_key["List"]:
        sync_key_list.append("%s_%s" % (item["Key"], item["Val"]))
    synckey = "|".join(sync_key_list)
    params_update = {
        'synckey': synckey,
        '_': params['_'] + 1,
        'r': int(time.time() * 1000),
    }
    params.update(params_update)
    print("发起 r8 长轮训")
    try:
        r8 = s.get(url=url, params=params)
        print(r8.text)
    except requests.exceptions.ConnectionError as e:
        print("捕获到异常")
        params['_'] -= 1
        continue
    # 返回 'window.synccheck={retcode:"0",selector:"0"}' 则继续长轮训
    # 返回 'window.synccheck={retcode:"0",selector:"2"}' 则发起POST
    if r8.text == 'window.synccheck={retcode:"0",selector:"2"}':
        print("POST同步：webwxsync")
        sync_url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync"
        sync_params = {
            'lang': 'zh_CN',
            'skey': ticket['skey'],
            'sid': ticket['wxsid'],
            'pass_ticket': ticket['pass_ticket'],
        }
        json_data["SyncKey"] = json.loads(r5.text)["SyncKey"]  # 在之前r5的基础上加一个SyncKey字典
        r9 = s.post(sync_url, params=sync_params, json=json_data)
        # r9.encoding = r9.apparent_encoding
        print(r9.apparent_encoding)  # 自动获取到的编码还是有问题
        r9.encoding = 'utf-8'
        # print(r9.text)
        r9_obj = json.loads(r9.text)
        add_msg_count = r9_obj['AddMsgCount']
        print("你有 %s 条消息" % add_msg_count)
        add_msg_list = r9_obj['AddMsgList']
        for add_msg in add_msg_list:
            content = add_msg["Content"]
            from_user_name = add_msg["FromUserName"]
            print(content, "<==", from_user_name)
        sync_key = json.loads(r9.text)["SyncKey"]  # 这里会多2条SyncKey


